﻿using Beaker.OpenCube.Render.Maths;
using Beaker.OpenCube.Render.Shapes;

namespace Beaker.OpenCube.Render.Tracing
{
	public sealed class RayTracer
		: ITracer
	{
		private readonly int _maxDepth;

		public RayTracer( int maxDepth = 5 )
		{
			_maxDepth = maxDepth;
		}

		/// <summary>
		/// Traces the given ray.
		/// </summary>
		/// <param name="scene">The scene.</param>
		/// <param name="ray">The ray.</param>
		/// <param name="depth">The current trace depth.</param>
		/// <returns>The final color of the traced ray.</returns>
		public Color TraceRay( Scene scene, Ray ray, int depth = 0 )
		{
			Intersection intersection = FindClosestIntersection( scene, ray );
			
			// no intersection so, return background color
			if( intersection == null )
				return Color.Background;

			// determine color by calling the shading function of the material of the object
			return intersection.Material.Shade( intersection );
		}

		/// <summary>
		/// Finds the closest intersection.
		/// </summary>
		/// <param name="scene">The scene.</param>
		/// <param name="ray">The ray.</param>
		/// <returns>The closest intersection, or <c>null</c> when no intersection was found.</returns>
		private Intersection FindClosestIntersection( Scene scene, Ray ray )
		{
			double closests = double.PositiveInfinity;
			Intersection closestsIntersection = null;

			// loop over all shapes
			// TODO: Add some sort of culling
			foreach( IShape shape in scene.Shapes )
			{
				// computer intesection of ray with shape
				Intersection intersection = shape.ComputeIntersection( scene, ray );
				// closer as the previous?
				if( intersection != null && intersection.Distance < closests )
				{
					// we have a new winner
					closestsIntersection = intersection;
					closests = intersection.Distance;
				}
			}

			// return closest intersection
			return closestsIntersection;
		}

	}

	/*
	public sealed class OriginalRayTracer
		: ITracer
	{
		private readonly int _maxDepth;

		public OriginalRayTracer( int maxDepth = 5 )
		{
			_maxDepth = maxDepth;
		}

		private Intersection Intersections( Scene scene, Ray ray )
		{
			double closests = double.PositiveInfinity;
			Intersection closestsIntersection = null;

			foreach( IShape shape in scene.Shapes )
			{
				Intersection intersection = shape.ComputeIntersection( scene, ray );
				if( intersection != null && intersection.Distance < closests )
				{
					closestsIntersection = intersection;
					closests = intersection.Distance;
				}
			}

			return closestsIntersection;
		}

		public Color TraceRay( Scene scene, Ray ray, int depth = 0 )
		{
			var intersection = Intersections( scene, ray );
			if( intersection == null )
				return Color.Background;
			else
				return Shade( scene, intersection, depth );
		}

		private Color Shade( Scene scene, Intersection intersection, int depth )
		{
			Vector3 direction = intersection.Ray.Direction;
			Vector3 normal = intersection.Normal;

			Vector3 reflectionDirection = direction - normal * ( ( normal * direction ) * 2 );

			Color naturalColor =
				Color.Background
					.Add(
						GetNaturalColor( scene, intersection, reflectionDirection )
					);
			Color reflectedColor =
				( depth > _maxDepth )
					? Color.Gray
					: GetReflectionColor( scene, intersection, reflectionDirection, depth );
			return naturalColor.Add( reflectedColor );
		}

		private Color GetReflectionColor( Scene scene, Intersection intersection, Vector3 reflectionDirection, int depth = 0 )
		{
			return
				TraceRay( scene, new Ray( intersection.HitPoint, reflectionDirection ), depth + 1 )
					.Multiply( intersection.Material.Reflect( intersection ) );
		}

		private Color GetNaturalColor( Scene scene, Intersection intersection, Vector3 reflectionDirection )
		{
			Point3 pos = intersection.HitPoint;
			Vector3 normal = intersection.Normal;
			IMaterial material = intersection.Material;

			return
				scene.Lights.Aggregate(
					Color.DefaultColor,
					( color, light ) =>
					{
						Vector3 lightVector = light.Position - pos;
						Vector3 lightDirection = lightVector.Normalize();
						Intersection neatIsect = Intersections( scene, new Ray( pos, lightDirection ) );
						bool isInShadow = neatIsect != null && neatIsect.Distance <= lightVector.Magnitude();
						if( isInShadow )
							return color;
						double illum = lightDirection * normal;
						Color lcolor = ( illum > 0d )
										? light.Color.Multiply( illum )
										: Color.DefaultColor;
						double specular = lightDirection * reflectionDirection.Normalize();
						Color scolor = ( specular > 0d )
										? light.Color.Multiply( Math.Pow( specular, material.Roughness( intersection ) ) )
										: Color.DefaultColor;
						return color +
								lcolor * material.Diffuse( intersection ) +
								scolor * material.Specular( intersection );
					}
				);
		}
	}
	 * */
}
